package nl.unimaas.micc.umsnooker.logic;

import java.util.HashSet;

import com.jme.math.Vector3f;

import nl.unimaas.micc.umsnooker.domain.BilliardBall;
import nl.unimaas.micc.umsnooker.domain.Player;
import nl.unimaas.micc.umsnooker.gameflow.GameState;
import nl.unimaas.micc.umsnooker.simulation.EventHandler;
import nl.unimaas.micc.umsnooker.simulation.event.BallCollision;
import nl.unimaas.micc.umsnooker.simulation.event.Event;
import nl.unimaas.micc.umsnooker.simulation.event.PocketingEvent;

public class GameLogicHandler implements EventHandler
{
	private GameState state;
	
	private BilliardBall.Type onBall;
	
	private Player currentPlayer;
	private Player nextPlayer;
	
	private boolean cueBallPocketed = false;
	private int pottedScore = 0;
	private int foulScore = 0;
	private boolean foulOccured = false;
	private boolean firstBallCollision = true;
	private HashSet<BilliardBall> pottedColouredBalls = new HashSet<BilliardBall>();
	
	public GameLogicHandler(GameState state)
	{
		this.state = state;
		
		if (state.getOnBalls() == GameState.redBalls)
		{
			onBall = BilliardBall.Type.RED;
		}

		currentPlayer = state.getCurrentPlayer();
		
		int playerIndex = state.getPlayers().indexOf(currentPlayer);
		nextPlayer = state.getPlayers().get((playerIndex+1) % state.getPlayers().size());
	}
	
	@Override
	public void handle(Event e)
	{
		if (e instanceof PocketingEvent)
		{
			handlePocketCollision((PocketingEvent) e);
		} else if (e instanceof BallCollision)
		{
			handleBallInteraction((BallCollision) e);
		}
	}

	private void handleBallInteraction(BallCollision e)
	{
		if (firstBallCollision)
		{
			firstBallCollision = false;
			for (BilliardBall b : e.getBallKeys())
			{
				if (!b.equals(state.getCueBall()))
				{
					if (state.getOnBalls().contains(b.getType()))
					{
						if (state.getOnBalls() == GameState.colourBalls)
						{
							// colored balls were on, so the first touched ball
							// will be the only one on
							onBall = b.getType();
						}
					} else
					// touched ball is not on
					{
						foulOccured = true;
						foulScore = Math.max(foulScore, b.getType().getValue());
					}
					return; // there is only one non-cue ball in the first
							// collision
				}
			}
		}
	}

	private void handlePocketCollision(PocketingEvent e)
	{
		for (BilliardBall b : e.getBallKeys())
		{
			if (b.equals(state.getCueBall()))
			{
				foulOccured = true;
				cueBallPocketed = true;
			} else
			{
				if (onBall == null && state.getOnBalls().contains(b.getType()))
				{
					onBall = b.getType();
					pottedScore += b.getType().getValue();
				}
				else if (b.getType() == onBall)
				{
					pottedScore += b.getType().getValue();
				} else
				{
					foulOccured = true;
					foulScore = Math.max(foulScore, b.getType().getValue());
				}
				pottedColouredBalls.add(b);
			}
		}
	}
	
	
	public void evaluateEvents()
	{		
		if (firstBallCollision)
		{
			foulOccured = true;
		}
		
		// scores
		if (foulOccured)
		{
			foulScore = Math.max(4, foulScore);
		}
	}
	
	public int getPottedScore()
	{
		return pottedScore;
	}
	
	public int getFoulScore()
	{
		return foulScore;
	}
	
	public void updateState()
	{
		evaluateEvents();
		
		if (foulOccured)
		{
			nextPlayer.setScore(nextPlayer.getScore() + foulScore);
		}
		else
		{
			currentPlayer.setScore(currentPlayer.getScore() + pottedScore);
		}
		
		//TODO start with highest values
		for (BilliardBall b : pottedColouredBalls)
		{
			if (GameState.colourBalls.contains(b.getType()))
			{
				Vector3f[] initialSpots = new Vector3f[]{
					state.getTable().getInitialSpot(b.getType()),
					state.getTable().getBlackSpot(),
					state.getTable().getPinkSpot(),
					state.getTable().getBlueSpot(),
					state.getTable().getBrownSpot(),
					state.getTable().getGreenSpot(),
					state.getTable().getYellowSpot()
				};
				
				Vector3f spot = initialSpots[0];
				boolean foundSpot = false;
				for (Vector3f initialSpot : initialSpots)
				{
					if (foundSpot)
					{
						break;
					}
					if (state.isSpotFree(initialSpot))
					{
						spot = initialSpot;
						foundSpot = true;
					}
				}
				if (!foundSpot)
				{
					spot = state.getTable().getInitialSpot(b.getType());
					float step = .001f;
					while (!foundSpot)
					{
						spot.y += step;
						if (spot.y > state.getTable().getLength() - b.getRadius())
						{
							spot = state.getTable().getInitialSpot(b.getType());
							step = -step;
							continue;
						}
						if (state.isSpotFree(spot))
						{
							foundSpot = true;
						}
					}
				}
				
				boolean foundBall = false;
				for (BilliardBall ball : state.getBalls())
				{
					if (foundBall)
					{
						break;
					}
					if (ball.equals(b))
					{
						ball.setPosition(spot);
						foundBall = true;
					}
				}
			}
			else if (GameState.redBalls.contains(b.getType()))
			{
				state.getBalls().remove(b);
			}
		}
		
		if (foulOccured || pottedScore == 0)
		{
			// switch player
			state.setCurrentPlayer(nextPlayer);
			state.setOnBalls(GameState.redBalls);
		}
		else
		{
			// same player
			if (pottedScore > 0)
			{
				// switch balls on
				if (state.getOnBalls() == GameState.redBalls)
				{
					state.setOnBalls(GameState.colourBalls);
				}
				else
				{
					state.setOnBalls(GameState.redBalls);
				}
			}
		}	
	}
	
	public boolean cueBallPocketed()
	{
		return cueBallPocketed;
	}
	
	public boolean foulOccured()
	{
		return foulOccured;
	}
}
